

import Foundation


public struct NDT7Settings {

  
    public let timeout: NDT7Timeouts

   
    public let skipTLSCertificateVerification: Bool

   
    public let headers: [String: String]

    public var allServers: [NDT7Server] = []

    public var currentServerIndex: Int?

    public var currentServer: NDT7Server? {
        guard let selectedIndex = currentServerIndex,
              selectedIndex < allServers.count else { return nil }
        return allServers[selectedIndex]
    }

    public var currentDownloadURL: URL? {
        guard let downloadPath = currentServer?.urls.downloadPath else { return nil }
        return URL(string: downloadPath)
    }

    public var currentUploadURL: URL? {
        guard let uploadPath = currentServer?.urls.uploadPath else { return nil }
        return URL(string: uploadPath)
    }

    public init(timeout: NDT7Timeouts = NDT7Timeouts(),
                skipTLSCertificateVerification: Bool = false,
                headers: [String: String] = [NDT7WebSocketConstants.Request.headerKey: NDT7WebSocketConstants.Request.headerProtocolValue]) {
        self.skipTLSCertificateVerification = skipTLSCertificateVerification
        self.timeout = timeout
        self.headers = headers
    }
}

public struct NDT7Timeouts {

  
    public let measurement: TimeInterval

    public let ioTimeout: TimeInterval

    public let downloadTimeout: TimeInterval

    public let uploadTimeout: TimeInterval

    public init(measurement: TimeInterval = NDT7WebSocketConstants.Request.updateInterval,
                ioTimeout: TimeInterval = NDT7WebSocketConstants.Request.ioTimeout,
                downloadTimeout: TimeInterval = NDT7WebSocketConstants.Request.downloadTimeout,
                uploadTimeout: TimeInterval = NDT7WebSocketConstants.Request.uploadTimeout) {
        self.measurement = measurement >= NDT7WebSocketConstants.Request.updateInterval ? measurement : NDT7WebSocketConstants.Request.updateInterval
        self.ioTimeout = ioTimeout
        self.downloadTimeout = downloadTimeout
        self.uploadTimeout = uploadTimeout
    }
}

public struct LocateAPIResponse: Codable {
    public var results: [NDT7Server]
}

public struct NDT7Server: Codable {

    public var machine: String

    public var location: NDT7Location?

    public var urls: NDT7URLs
}

public struct NDT7Location: Codable {

    public var country: String?

 
    public var city: String?
}


public struct NDT7URLs: Codable {

    public var downloadPath: String


    public var uploadPath: String

    public var insecureDownloadPath: String

   
    public var insecureUploadPath: String

    enum CodingKeys: String, CodingKey {
        case downloadPath = "wss:///ndt/v7/download"
        case uploadPath = "wss:///ndt/v7/upload"
        case insecureDownloadPath = "ws:///ndt/v7/download"
        case insecureUploadPath = "ws:///ndt/v7/upload"
    }
}

extension NDT7Server {

   
    public static func discover<T: URLSessionNDT7>(session: T = URLSession.shared as! T,
                                                   retry: UInt = 0,
                                                   _ completion: @escaping (_ server: [NDT7Server]?, _ error: NSError?) -> Void) -> URLSessionTaskNDT7 {
        let retry = min(retry, 4)
        let request = Networking.urlRequest(NDT7WebSocketConstants.MLabServerDiscover.url)
        let task = session.ndt7DataTask(with: request as URLRequest) { (data, _, error) -> Void in
            OperationQueue.current?.name = "net.measurementlab.NDT7.MlabServer.discover"
            guard error?.localizedDescription != "cancelled" else {
                completion(nil, NDT7TestConstants.cancelledError)
                return
            }
            guard error == nil, let data = data else {
                if retry > 0 {
                    logNDT7("NDT7 MLab cannot find a suitable MLab server, retry: \(retry)", .info)
                    DispatchQueue.global().asyncAfter(deadline: .now() + 0.5) {
                        _ = discover(session: session, retry: retry - 1, completion)
                    }
                } else {
                    completion(nil, NDT7WebSocketConstants.MLabServerDiscover.noMLabServerError)
                }
                return
            }

            do {
                let apiResponse = try JSONDecoder().decode(LocateAPIResponse.self, from: data)
                completion(apiResponse.results, nil)
            } catch let jsonError as NSError {
                logNDT7("JSON decode failed: \(jsonError.localizedDescription)")
                completion(nil, NDT7WebSocketConstants.MLabServerDiscover.noMLabServerError)
            }

            return
        }
        task.resume()
        return task
    }
}
